﻿using System;
using System.Collections.Generic;
using System.Linq;
using Codeflows.Dispatch.ResultHandlers;
using Codeflows.Extensions.Tracking;
using Codeflows.Tracking;
using Codeflows.Results;
using System.Threading.Tasks;

namespace Codeflows.Dispatch
{
    class Dispatcher
    {
        readonly Guid _id;
        readonly ActionMethodDescriptor[] _actionMethodDescriptors;
        readonly CodeflowInstance _codeflowInstance;
        readonly Codeflow _codeflow;
        private readonly ICodeflowTrackerFactory _trackerFactory;
        readonly Dictionary<Type, ResultHandlerBase> _resultHandlers;

        public Dispatcher(Guid id, CodeflowInstance codeflowInstance, Codeflow codeflow, ICodeflowTrackerFactory trackerFactory, ActionMethodDescriptor[] actionMethodDescriptors)
        {
            if (codeflowInstance == null) throw new ArgumentNullException("codeflowInstance");
            if (trackerFactory == null) throw new ArgumentNullException("trackerFactory");
            if (actionMethodDescriptors == null) throw new ArgumentNullException("actionMethodDescriptors");

            _id = id;
            _trackerFactory = trackerFactory;
            _actionMethodDescriptors = actionMethodDescriptors;
            _codeflowInstance = codeflowInstance;
            _codeflow = codeflow;
            _trackerFactory = trackerFactory;

            _resultHandlers = new Dictionary<Type, ResultHandlerBase>
                                  {
                                      {typeof (DelayedJumpResult), new SleepHandler()},
                                      {typeof (EndResult), new EndHandler()},
                                      {typeof (JumpResult), new JumpHandler()},
                                      {typeof (PausedResult), new PauseHandler()},
                                      {typeof (PendingResult), new PendingHandler(ResumeDispatcher)},
                                      {typeof (RetryResult), new RetryHandler()},
                                      {typeof (SuspendedResult), new SuspendHandler()}
                                  };
        }

        public Result ExecuteFrom(string actionName, IDictionary<string, object> arguments)
        {
            _codeflowInstance.Resume(actionName, arguments);
            _trackerFactory.Update(_codeflowInstance);
            return ExecuteCore(null);
        }

        public Result Execute()
        {
            return ExecuteCore(null);
        }

// ReSharper disable RedundantAssignment
        Result ExecuteCore(Result result)
// ReSharper restore RedundantAssignment
        {
            while (true)
            {
                _codeflowInstance.Run();
                _trackerFactory.Update(_codeflowInstance);

                var actionMethodDescriptor =
                    _actionMethodDescriptors.SingleOrDefault(a => a.Name == _codeflowInstance.CurrentAction.Name);

                if (actionMethodDescriptor == null)
                {
                    _codeflowInstance.Terminate();
                    _trackerFactory.Update(_codeflowInstance);
                    throw DiagnosticsUtility.CreateException("Unable to find the target of the jump.");
                }

                DiagnosticsUtility.Verbose("Attempt #{0} Executing action {1}",
                                           _codeflowInstance.CurrentAction.DispatchAttemptsCount,
                                           _codeflowInstance.CurrentAction.Name);

                result = actionMethodDescriptor.ExecuteAction(_codeflow, _codeflowInstance);
                
                DiagnosticsUtility.Verbose("Attempt #{0} Completed with a {1}",
                                           _codeflowInstance.CurrentAction.DispatchAttemptsCount, result);

                if (!HandleResult(result))                                    
                    break;                
            }

            ThrowIfSuspended(result);
            return result;
        }

        static void ThrowIfSuspended(Result result)
        {
            var suspendedResult = result as SuspendedResult;
            if (suspendedResult != null)
                throw new CodeflowException(
                    "Codeflow encountered an unhandled exception. Check inner exception for more details.",
                    suspendedResult.Exception);
        }

        Result ResumeDispatcher(Task<Result> task)
        {
            return HandleResult(task.Result) ? ExecuteCore(task.Result) : task.Result;                   
        }

        bool HandleResult(Result result)
        {
            var handler = _resultHandlers[result.GetType()];
            handler.Execute(_id, _codeflowInstance, result);
            _trackerFactory.Update(_codeflowInstance);
            return handler.CanContinueDispatchLoop;
        }
    }
}